package logic;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;

import logic.GraphProcessing.DepthFirstIterator;

import data.Node;

/**
 * 
 * @author "Daniel Pardo"
 * The GraphGenerator class provides methods to generate a random amount of nodes with randomly set links between those nodes
 * <p>
 * A internal counter for the nodeID is used to guarantee that same values for nodes are never used.
 * <pre>private static int numberOfNodes </pre>
 * <p>
 * Two members define a graph. A List for the nodes and a HashMap of the neighborhood nodes with the ID as the key value
 * <p>
 * The GraphGenerator uses randomly generated values for the neighbors of a node. To obtain such values the java.util.Random class is used.
 */
public class GraphGenerator {

	private static int numberOfNodes = 0;

	public static class Graph {	
		/**
		 * constructor for a graph
		 * @param nodeList
		 * @param hashMap
		 */
		public Graph(List<Node> nodeList, HashMap<Integer, LinkedList<Node>> hashMap) {
			super();
			Graph.nodeList = nodeList;
			Graph.neighbors = hashMap;
		}

		/**
		 * @return the nodeList
		 */
		public List<Node> getNodeList() {
			return nodeList;
		}
		/**
		 * @param nodeList the nodeList to set
		 */
		public void setNodeList(List<Node> nodeList) {
			Graph.nodeList = nodeList;
		}
		/**
		 * @return the neighbors
		 */
		public HashMap<Integer, LinkedList<Node>> getNeighbors() {
			return neighbors;
		}
		/**
		 * @param hashMap the neighbors to set
		 */
		public void setNeighbors(HashMap<Integer, LinkedList<Node>> hashMap) {
			Graph.neighbors = hashMap;
		}

		private static List<Node> nodeList = new LinkedList<Node>();
		private static HashMap<Integer, LinkedList<Node>> neighbors;

		/**
		 * string representation
		 */
		public String toString() {
			String ret = "";
			for (int j=0; j < nodeList.size(); j++ ) { //traverse nodeList
				Node node = nodeList.get(j);
				ret = ret + node.toString();		
				ret = ret + "\n";

				/*LinkedList<Node> neighborNodes = neighbors.get(node.getID());
				if (neighborNodes!=null) {
					for (int i=0; i < neighborNodes.size(); i++) { //traverse neighbors of current node
						Node neighbor = neighborNodes.get(i);
						ret = ret + "\t" + neighbor.toString();
						ret = ret + "\n";
					}
				}
				else {
					ret = ret + "\t-" + "\n";
				}
				ret = ret + "\n";*/
			}

			return ret;
		}

		/**
		 * Saves a graph.<p>the format is shown below<p>
		 * <pre>
		 * <p>graph with 5 nodes and 4 edges<p>
		 * 5
		 * 0|10.0|Node0|default.png
		 * 1|10.0|Node1|default.png
		 * 2|10.0|Node2|default.png
		 * 3|10.0|Node3|default.png
		 * 4|10.0|Node4|default.png
		 * 5
		 * 0|4
		 * 1|0|3
		 * 2
		 * 3
		 * 4|2
		 * </pre>
		 * @param filename
		 * @param gp
		 */
		public static void saveAbsolute(String filename, GraphProcessing gp) {
			try {
				PrintWriter pw = new PrintWriter(filename);
				//size of list of nodes
				DepthFirstIterator iterator = gp.iteratorDepthFirst();
				int counter = 0;
				while(iterator.hasNext()){
					iterator.next();
					counter++;
				}
				pw.println( String.valueOf(counter) );
				iterator = gp.iteratorDepthFirst();
				//write nodes
				while (iterator.hasNext()) {
					Node node = iterator.next();
					pw.println( String.valueOf(node.getID()) + "|" + 
							String.valueOf(node.getSize()) + "|" + 
							String.valueOf(node.getName()) + "|" + 
							String.valueOf(node.getGraphic()) );
				}
				//size of list of neighbors
				pw.println( String.valueOf(counter) );
				iterator = gp.iteratorDepthFirst();
				//write neighbors
				while (iterator.hasNext()) {
					Node node = iterator.next();
					Iterator<Node> neighborsIt = node.getNeighbors().iterator();
					pw.print( String.valueOf(node.getID()) );
					while (neighborsIt.hasNext()) {
						Node nghbr = neighborsIt.next();
						pw.print( "|" + String.valueOf(nghbr.getID()) );	
					}
					pw.println();
				}
				pw.flush();
				pw.close();
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			}		
		}

		/**
		 * Saves a graph.<p>the format is shown below<p>
		 * <pre>
		 * <p>graph with 5 nodes and 4 edges<p>
		 * 5
		 * 0|10.0|Node0|default.png
		 * 1|10.0|Node1|default.png
		 * 2|10.0|Node2|default.png
		 * 3|10.0|Node3|default.png
		 * 4|10.0|Node4|default.png
		 * 5
		 * 0|4
		 * 1|0|3
		 * 2
		 * 3
		 * 4|2
		 * </pre>
		 * @param filename in "/data" subfolder
		 */
		public void save(String filename) {
			try {
				PrintWriter pw = new PrintWriter("data" + File.separator + filename);
				//size of list of nodes
				pw.println( String.valueOf(nodeList.size()) );
				Iterator<Node> nodeIt = nodeList.iterator();
				//write nodes
				while (nodeIt.hasNext()) {
					Node node = nodeIt.next();
					pw.println( String.valueOf(node.getID()) + "|" + 
							String.valueOf(node.getSize()) + "|" + 
							String.valueOf(node.getName()) + "|" + 
							String.valueOf(node.getGraphic()) );
				}
				//size of list of neighbors
				pw.println( String.valueOf(neighbors.size()) );
				nodeIt = nodeList.iterator();
				//write neighbors
				while (nodeIt.hasNext()) {
					Node node = nodeIt.next();
					Iterator<Node> neighborsIt = neighbors.get(node.getID()).iterator();
					pw.print( String.valueOf(node.getID()) );
					while (neighborsIt.hasNext()) {
						Node nghbr = neighborsIt.next();
						pw.print( "|" + String.valueOf(nghbr.getID()) );	
					}
					pw.println();
				}
				pw.flush();
				pw.close();
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			}		
		}

		/**
		 * Loads a graph.
		 * @param  filename in "/data" subfolder
		 * @return the graph
		 */
		public static Graph load(String filename) {

			Graph graph = new Graph(new LinkedList<Node>(), new HashMap<Integer, LinkedList<Node>>());

			FileInputStream fstream;
			try {
				fstream = new FileInputStream("data" + File.separator + filename);
				DataInputStream in = new DataInputStream(fstream);
				BufferedReader br = new BufferedReader(new InputStreamReader(in));



				int nodeListSize = Integer.valueOf(br.readLine());
				for (int i=0; i<nodeListSize; ++i)
				{					
					String read = br.readLine();
					String[] nodeData = read.split("\\|");
					int nodeID = Integer.valueOf(nodeData[0]);
					float nodeSize = Float.valueOf(nodeData[1]);
					String nodeName = nodeData[2];
					String nodeGraphic = nodeData[3];

					Node node = new Node(nodeID, nodeSize, nodeName, nodeGraphic);
					graph.getNodeList().add(node);
				}

				String read = br.readLine();
				int neighborListSize = Integer.valueOf(read);
				//for (int i=0; i<nodeListSize; ++i)
				//{
				for (int j=0; j<neighborListSize; ++j)
				{					
					String[] nodeData = br.readLine().split("\\|");
					int nodeID = Integer.valueOf(nodeData[0]);
					graph.getNeighbors().put(nodeID, new LinkedList<Node>());

					for (int k=1; k<nodeData.length; ++k)
					{
						int nghbrID = Integer.valueOf(nodeData[k]);
						Node addNode = graph.getNodeList().get(nghbrID);

						graph.getNeighbors().get(nodeID).add( addNode );
					}
					graph.getNodeList().get(nodeID).setNeighbors(graph.getNeighbors().get(nodeID));
				}
				//}

				in.close();//Close the input stream
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}

			return graph;
		}
		/**
		 * Loads a graph.
		 * @param  filename absolute Filename
		 * @return the graph
		 */
		public static Graph loadAbsolute(String filename) {
			Node.maxID = -1;
			Graph graph = new Graph(new LinkedList<Node>(), new HashMap<Integer, LinkedList<Node>>());

			FileInputStream fstream;
			try {
				fstream = new FileInputStream(filename);
				DataInputStream in = new DataInputStream(fstream);
				BufferedReader br = new BufferedReader(new InputStreamReader(in));



				int nodeListSize = Integer.valueOf(br.readLine());
				for (int i=0; i<nodeListSize; ++i)
				{					
					String read = br.readLine();
					String[] nodeData = read.split("\\|");
					int nodeID = Integer.valueOf(nodeData[0]);
					float nodeSize = Float.valueOf(nodeData[1]);
					String nodeName = nodeData[2];
					String nodeGraphic = nodeData[3];

					Node node = new Node(nodeID, nodeSize, nodeName, nodeGraphic);
					graph.getNodeList().add(node);
				}

				String read = br.readLine();
				int neighborListSize = Integer.valueOf(read);
				//for (int i=0; i<nodeListSize; ++i)
				//{
				
				for (int j=0; j<neighborListSize; ++j)
				{					
					String[] nodeData = br.readLine().split("\\|");
					int nodeID = Integer.valueOf(nodeData[0]);
					graph.getNeighbors().put(nodeID, new LinkedList<Node>());

					for (int k=1; k<nodeData.length; ++k)
					{
						int nghbrID = Integer.valueOf(nodeData[k]);
						Node addNode = null;// = graph.getNodeList().get(nghbrID);
						Iterator<Node>it2 = graph.getNodeList().iterator();
						while(it2.hasNext()){
							addNode = it2.next();
							if(nghbrID == addNode.getID())
								break;
						}
						graph.getNeighbors().get(nodeID).add( addNode );
					}
					Node node = null;// = graph.getNodeList().get(nghbrID);
					Iterator<Node>it2 = graph.getNodeList().iterator();
					while(it2.hasNext()){
						node = it2.next();
						if(nodeID == node.getID())
							break;
					}
					node.setNeighbors(graph.getNeighbors().get(nodeID));
				}
				//}

				in.close();//Close the input stream
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}

			return graph;
		}
	}
	/**
	 * Generates a random graph.
	 * @param numNodes the number of nodes
	 * @param nodeSize the size of nodes
	 * @return the generated graph
	 */
	public static Graph GenerateRandomGraph_constNodeSize(int numNodes, int nodeSize)
	{
		Graph graph = new Graph(new LinkedList<Node>(), new HashMap<Integer, LinkedList<Node>>());

		numberOfNodes = 0;
		for (int i = 0; i < numNodes; ++i) {	
			//add node
			Node node = new Node(numberOfNodes, nodeSize, "Node" + String.valueOf(i), "default.png");			
			graph.getNodeList().add(node);

			//increase id counter
			numberOfNodes++;
		}

		Iterator<Node> nodeIter = graph.getNodeList().iterator();
		List<List<Integer>> intList = new LinkedList<List<Integer>>();
		for (int i=0; i<graph.getNodeList().size(); i++) {
			List<Integer> list = new LinkedList<Integer>();
			intList.add(list);
		}

		nodeIter = graph.getNodeList().iterator();
		while (nodeIter.hasNext()) {
			Node node = nodeIter.next();
			List<Integer> list = getIDs(node.getID(), numNodes, Math.round((float)Math.log1p(numNodes)) );

			intList.get(node.getID()).addAll(list);

			Iterator<Integer> intIter = list.iterator();
			while (intIter.hasNext())
			{
				Integer index = intIter.next();
				intList.get(index).add(node.getID());
			}
		}

		nodeIter = graph.getNodeList().iterator();
		while (nodeIter.hasNext()) {
			Node node = nodeIter.next();
			List<Integer> nbList = intList.get(node.getID());

			//			//random neighborhood List
			//			List<Integer> intList = getIDs(node.getID(), numNodes, Math.round((float)Math.log1p(numNodes))/2 );
			//			
			//generate neighborhood List
			LinkedList<Node> idList = new LinkedList<Node>();

			//set values from randomized index list to neighborhood List
			for (int k = 0; k < nbList.size(); ++k)
			{
				Integer index = nbList.get(k);
				Node addNode = graph.getNodeList().get(index);

				idList.add( addNode );
			}

			//set ngbs in node
			node.setNeighbors(idList);
			//set ngbs in indexed ngb list
			graph.getNeighbors().put(node.getID(), idList);
		}


		return graph;
	}

	/**
	 * generate randomized Neighbor node IDs for a node (the nodes id is set in forbiddenID)
	 * 
	 * <p>example: getIDs(6, 20, 10) get randomized neighborhood IDs for node with id 6</p>
	 * 
	 * @param nodeID the reference node
	 * @param numNodes the total number of nodes
	 * @param attempts 
	 * @return the list of neighbor node IDs that are linked to the given node
	 */
	private static List<Integer> getIDs(int nodeID, int numNodes, int attempts)
	{
		List<Integer> intList = new LinkedList<Integer>();

		Random generator = new Random();

		for (int i=0; i<attempts; i++)
		{	
			if (generator.nextBoolean() == false) // random value {'true', 'false'}
				continue;

			int neighborNodeID = generator.nextInt(numNodes); //get random index for neighbor node

			if (neighborNodeID == nodeID) //leave out the id for the reference node
				continue;

			if (!intList.contains(neighborNodeID)) //... and add if not yet added to list
				intList.add(neighborNodeID);
		}

		return intList; // return list
	}

}
